package com.mind.core.net.websocket.server;

import static io.netty.handler.codec.http.HttpHeaders.isKeepAlive;
import static io.netty.handler.codec.http.HttpHeaders.setContentLength;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpHeaders.Names.HOST;
import static io.netty.handler.codec.http.HttpMethod.GET;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN;
import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import io.netty.util.AttributeKey;
import io.netty.util.CharsetUtil;

import org.apache.log4j.Logger;

import com.mind.core.net.IoHandler;
import com.mind.core.net.codec.GameEncoder;
import com.mind.core.net.handler.HandlerManager;
import com.mind.core.net.model.IConnector;
import com.mind.core.net.msg.Message;
import com.mind.core.net.msg.MessageParseException;
import com.mind.core.net.msg.MessageType;
import com.mind.core.net.msg.PolicyMessage;
import com.mind.core.net.msg.sys.SessionMessage;
import com.mind.core.net.processor.MessageProcessorDispatcher;
import com.mind.core.net.websocket.codec.WebsocketBinaryDecoder;
import com.mind.core.net.websocket.codec.WebsocketBinaryEncoder;
import com.mind.core.server.DataHelper;

/**
 * Handles handshakes and messages
 */
public abstract class WebSocketIoHandler extends SimpleChannelInboundHandler implements IoHandler {
    private static final Logger logger = Logger.getLogger(WebSocketIoHandler.class);
    protected static final AttributeKey<IConnector> PLAYER = AttributeKey.valueOf("socket");
    private static final String WEBSOCKET_PATH = "/websocket";
    private WebSocketServerHandshaker handshaker;
    @Override
    protected void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof FullHttpRequest) {
            handleHttpRequest(ctx, (FullHttpRequest) msg);
        } else if (msg instanceof WebSocketFrame) {
            handleWebSocketFrame(ctx, (WebSocketFrame) msg);
        }else if(msg instanceof Message){
        	IConnector player = ctx.channel().attr(PLAYER).get();
    		if (player != null) {
    			Message base = (Message) msg;
    			base.setSender(player);
    			if (base.getMessageType() == MessageType.FLASH_POLICY) {
    				// 特殊的处理,响应Flash socket policy请求
    				PolicyMessage _pm = (PolicyMessage) msg;
    				_pm.initPolicy();
    				ctx.channel().writeAndFlush(msg);
    				return;
    			}
    			MessageProcessorDispatcher.getInstance().put(base);
    			this.handlerMessage(ctx, msg);
    		}
        }
    }
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }
    private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception {
        // Handle a bad request.
        if (!req.getDecoderResult().isSuccess()) {
            sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST));
            return;
        }

        // Allow only GET methods.
        if (req.getMethod() != GET) {
            sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN));
            return;
        }
        // Send the demo page and favicon.ico
        if ("/".equals(req.getUri())) {
            ByteBuf content = WebSocketServerIndexPage.getContent(getWebSocketLocation(req));
            FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, OK, content);

            res.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8");
            setContentLength(res, content.readableBytes());

            sendHttpResponse(ctx, req, res);
            return;
        }
        if ("/favicon.ico".equals(req.getUri())) {
            FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, NOT_FOUND);
            sendHttpResponse(ctx, req, res);
            return;
        }
        // Handshake
        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
                getWebSocketLocation(req), null, false);
        handshaker = wsFactory.newHandshaker(req);
        if (handshaker == null) {
        	WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        } else {
        	  handshaker.handshake(ctx.channel(), req);
        	  conneted(ctx);
        }
    }
    private void conneted(ChannelHandlerContext ctx){
    	Message msg = new SessionMessage(SessionMessage.SESSION_OPENED);
		IConnector basePlayer = this.createConnector();
		basePlayer.setChannel(ctx.channel());
		msg.setSender(basePlayer);
		MessageProcessorDispatcher.getInstance().put(msg);
		ctx.channel().attr(PLAYER).set(basePlayer);
    }
    private void disConneted(ChannelHandlerContext ctx){
    	IConnector player = ctx.channel().attr(PLAYER).get();
		if (player != null) {
			player.setChannel(null);
			DataHelper.getInstance().removeOnlinePlayer(player.id());
		}
		Message msg = new SessionMessage(SessionMessage.SESSION_CLOSED);
		msg.setSender(player);
		MessageProcessorDispatcher.getInstance().put(msg);
    }
    
    private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
        // Check for closing frame
        if (frame instanceof CloseWebSocketFrame) {
            disConneted(ctx);
            handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
            return;
        }
        if (frame instanceof PingWebSocketFrame) {
            ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
            return;
        }
        if(!(frame instanceof BinaryWebSocketFrame)){
        	throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass()
                  .getName()));
        }
//        decode(frame.content());
//        if (!(frame instanceof TextWebSocketFrame)) {
//            throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass()
//                    .getName()));
//        }
//        // Send the uppercase string back.
//        String request = ((TextWebSocketFrame) frame).text();
//        if (logger.isLoggable(Level.FINE)) {
//            logger.fine(String.format("%s received %s", ctx.channel(), request));
//        }
//        ctx.channel().write(new TextWebSocketFrame(request.toUpperCase()));
    }

    protected  void sendHttpResponse(
            ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) {
        // Generate an error page if response getStatus code is not OK (200).
        if (res.getStatus().code() != 200) {
            ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8);
            res.content().writeBytes(buf);
            buf.release();
            setContentLength(res, res.content().readableBytes());
        }

        // Send the response and close the connection if necessary.
        ChannelFuture f = ctx.channel().writeAndFlush(res);
        if (!isKeepAlive(req) || res.getStatus().code() != 200) {
            f.addListener(ChannelFutureListener.CLOSE);
        }
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        logger.info("connection is disconnect", cause);
        disConneted(ctx);
        ctx.close();
    }

    private static String getWebSocketLocation(FullHttpRequest req) {
        return "ws://" + req.headers().get(HOST) + WEBSOCKET_PATH;
    }
}
